package org.amse.ys.sndeditor.model.modules;

import java.io.IOException;

import org.amse.ys.sndeditor.model.IDrawer;
import org.amse.ys.sndeditor.model.IModule;
import org.amse.ys.sndeditor.model.ISoundFile;
import org.amse.ys.sndeditor.model.Localiser;
import org.amse.ys.sndeditor.model.drawer.AndroidDrawer;
import org.amse.ys.sndeditor.model.drawer.RequestBoolean;
import org.amse.ys.sndeditor.model.drawer.RequestString;
import org.amse.ys.sndeditor.ui.R.string;

public class SimpleDiagramModule implements IModule {
	// MAGIC_NUMBERS_IN_SECONDS / MAGIC_MODIFICATOR has name from MAGIC_NAMES
	public final double[] MAGIC_NUMBERS_IN_SECONDS = { 0.0001, 0.001, 0.01,
			0.1, 1, 15, 30, 60, 120, 300, 600, 3600 };
	public final double[] MAGIC_MODIFICATOR = { 0.001, 0.001, 0.001, 1, 1, 1,
			1, 60, 60, 60, 60, 3600 };
	public final String[] MAGIC_NAMES = new String[12];
	public final boolean[] MAGIC_HAS_DECIMALS = { true, false, false, true,
			false, false, false, false, false, false, false, false };

	private IDrawer myDrawer;
	private int myXSize;
	private int myYSize;

	private double myZoom = 1;
	private int myLeftBorder;
	private final String[] MENU_ITEMS = new String[5];
	private ISoundFile mySoundFile;
	private boolean myHasMoved = true;

	// helps
	boolean myShowPasteHelp = true;

	// for editing
	int mySelectionStart;
	int mySelectionEnd;
	long myPreviousTouchTime = 0;
	int myMarkerWidth;
	int myMarkerHeight;
	boolean myStartIsSelected;
	boolean myEndIsSelected;
	int myStartPreviousPosition;
	int myEndPreviousPosition;

	// Obligatory empty or simple methods
	public void onTimer() {
	}

	public String getMenuItemName(int numberOfItem) {
		return MENU_ITEMS[numberOfItem];
	}

	public int getNumberOfMenuOptions() {
		return MENU_ITEMS.length;
	}

	private void refreshBackground() {
		myDrawer.setColor(255, 0, 0, 0);
		myDrawer.drawRectangle(0, 0, myXSize, myYSize);
		int start = myLeftBorder;
		drawGrid(start);
		for (int i = 0; i < mySoundFile.giveNumberOfChannels(); i++) {
			paintHistogram(start, i);
		}
		myDrawer.saveBackgroud();
	}

	public void refresh() {
		if (!mySoundFile.isOpened()) {
			return;
		}
		if (myHasMoved) {
			refreshBackground();
			myHasMoved = false;
		} else {
			myDrawer.drawBackground();
		}
		paintSelection();
		// myDrawer.drawBackground();
		// paintSelection();

	}

	public void setSize(int width, int height) {
		myXSize = width;
		myYSize = height;
		myDrawer.setSize(width, height);
		myHasMoved = true;
		refresh();
	}

	public IDrawer getDrawer() {
		return myDrawer;
	}

	public int getTimerDelay() {
		return -1;
	}

	public boolean isMagnifyable() {
		return true;
	}

	public boolean isScreenChanged() {
		return myDrawer.isChanged();
	}

	{ // Initiliser making the drawer
		myDrawer = AndroidDrawer.getDrawer();
		myMarkerWidth = myDrawer
				.getPictureWidth(org.amse.ys.sndeditor.ui.R.drawable.selection_start);
		myMarkerHeight = myDrawer
				.getPictureHeight(org.amse.ys.sndeditor.ui.R.drawable.selection_start);
		MAGIC_NAMES[0] = Localiser.getString(string.ms);
		MAGIC_NAMES[1] = MAGIC_NAMES[0];
		MAGIC_NAMES[2] = MAGIC_NAMES[0];
		MAGIC_NAMES[3] = Localiser.getString(string.second);
		MAGIC_NAMES[4] = MAGIC_NAMES[3];
		MAGIC_NAMES[5] = MAGIC_NAMES[3];
		MAGIC_NAMES[6] = MAGIC_NAMES[3];
		MAGIC_NAMES[7] = Localiser.getString(string.minute);
		MAGIC_NAMES[8] = MAGIC_NAMES[7];
		MAGIC_NAMES[9] = MAGIC_NAMES[7];
		MAGIC_NAMES[10] = MAGIC_NAMES[7];
		MAGIC_NAMES[11] = Localiser.getString(string.hour);

		MENU_ITEMS[0] = Localiser.getString(string.show_all);
		MENU_ITEMS[1] = Localiser.getString(string.copy);
		MENU_ITEMS[2] = Localiser.getString(string.paste);
		MENU_ITEMS[3] = Localiser.getString(string.delete);
		MENU_ITEMS[4] = Localiser.getString(string.save);

	}

	public String getAppletName() {
		return Localiser.getString(string.simple_diagram);
	}

	public boolean isSelectable() {
		return true;
	}

	// WARNING! this method should be extremely simple
	public void initilize(ISoundFile fileToProceed) {
		mySoundFile = fileToProceed;
		// init markers and view
		if (mySoundFile.isOpened()) {
			mySelectionStart = fileToProceed.giveLengthInPoints() / 2;
			mySelectionEnd = mySelectionStart;
		} else {
			mySelectionStart = 0;
			mySelectionEnd = 0;
		}

		myZoom = 1;
		myLeftBorder = 0;
		myStartIsSelected = false;
		myEndIsSelected = false;
		myHasMoved = true;
		// TODO

	}

	public void onMagnify(double timesToMagnify) {
		myZoom = myZoom * timesToMagnify;
		if ((mySoundFile.giveLengthInPoints() - this.myLeftBorder) / myZoom < 0.05) {
			myZoom = (mySoundFile.giveLengthInPoints() - this.myLeftBorder) * 20;
		}
		enshureBorders();
		myHasMoved = true;
	}

	public void enshureBorders() {
		if (myLeftBorder < 0) {
			myLeftBorder = 0;
		} else if (unsafeConvertXToPoint(myXSize) >= mySoundFile
				.giveLengthInPoints()) {
			myLeftBorder -= unsafeConvertXToPoint(myXSize + 2)
					- mySoundFile.giveLengthInPoints();
			myLeftBorder = (int) Math.max(0, myLeftBorder);
		}
		myHasMoved = true;
		refresh();
	}

	public void onSpin(double spin) {
		myHasMoved = true;
		if (!mySoundFile.isOpened()) {
			return;
		}
		myLeftBorder += spin * mySoundFile.giveLengthInPoints() / (myZoom);
		enshureBorders();
	}

	public int getEndPosition() {
		return mySelectionEnd;
	}

	public int getStartPosition() {
		return mySelectionStart;
	}

	// Simple methods to implement

	public void onMenuItemSelected(int numberOfMenuOption) {
		myHasMoved = true;
		switch (numberOfMenuOption) {
		case 0: {
			// show all
			myZoom = 1;
			refresh();
			break;
		}

		case 1: {
			// copy
			mySoundFile.copyToClipBoard(mySelectionStart, mySelectionEnd);
			break;
		}
		case 2: {
			// paste
			if (myShowPasteHelp) {
				// myDrawer.showMessage(Localiser.getString(string.paste_help));
				myDrawer.showMessageWithCheckbox(Localiser
						.getString(string.paste_help), Localiser
						.getString(string.dont_show), new RequestBoolean() {
					@Override
					public void run(boolean s) {
						myShowPasteHelp = !s;

					}
				});
			} else {
				// Do nothing
			}
			mySoundFile.pasteFromClipBoard(mySelectionStart);
			mySelectionEnd = mySelectionStart
					+ mySoundFile.giveBufferLengthInPoints();
			break;
		}

		case 3: {
			// cut
			mySoundFile.cut(mySelectionStart, mySelectionEnd);
			mySelectionEnd = mySelectionStart;
			break;
		}

		case 4: {
			// save
			myDrawer.saveFileDialog(new RequestString() {
				public void run(String s) {
					try {
						mySoundFile.saveToFile(s);
						myDrawer.showMessage(Localiser
								.getString(string.file_saved)
								+ s);
					} catch (IOException e) {
						myDrawer.showMessage(Localiser
								.getString(string.file_save_error)
								+ s);
					}
				}
			});
			break;
		}

		}
		refresh();
	}

	private boolean startMoveIsAllowed(int position) {
		if (position > mySelectionEnd) {
			return false;
		} else {
			return true;
		}
	}

	private boolean endMoveIsAllowed(int position) {
		if (position < mySelectionStart) {
			return false;
		} else {
			return true;
		}
	}

	public void onTouch(int posX, int posY, long time) {
		if (myPreviousTouchTime == time) {
			// long touching
			if (myStartIsSelected) {
				// put here the runner
				if (startMoveIsAllowed(convertXToPoint(posX))) {
					mySelectionStart = convertXToPoint(posX);
				} else {
					mySelectionStart = mySelectionEnd;
				}
			} else if (myEndIsSelected) {
				// put here the runner

				if (endMoveIsAllowed(convertXToPoint(posX))) {
					mySelectionEnd = convertXToPoint(posX);
				} else {
					mySelectionEnd = mySelectionStart;
				}
			} else {
				// scroll
				myHasMoved = true;
			}
		} else {
			// a new touch takes place
			myStartIsSelected = false;
			myEndIsSelected = false;

			int left = convertPointToXPosition(mySelectionStart);
			int right = convertPointToXPosition(mySelectionEnd);
			if ((Math.abs(posX - left) < myMarkerWidth)
					&& (startMoveIsAllowed(convertXToPoint(posX)))) {
				// we select start
				myStartPreviousPosition = mySelectionStart;
				mySelectionStart = convertXToPoint(posX);
				myStartIsSelected = true;
			} else if ((Math.abs(posX - right) < this.myMarkerWidth)
					&& (endMoveIsAllowed(convertXToPoint(posX)))) {
				// we select end
				myEndPreviousPosition = mySelectionEnd;
				mySelectionEnd = convertXToPoint(posX);
				myEndIsSelected = true;
			} else {
				// we scroll
				myHasMoved = true;
			}
		}
		refresh();
		myPreviousTouchTime = time;
	}

	// Quite complicated methods
	private int unsafeConvertXToPoint(int xPosition) {
		return (int) (filePointsPerStep() * xPosition + myLeftBorder);
	}

	private int convertXToPoint(int xPosition) {
		return Math.min((int) (filePointsPerStep() * xPosition + myLeftBorder),
				mySoundFile.giveLengthInPoints());
	}

	private int convertPointToXPosition(int point) {
		return (int) ((point - myLeftBorder) / filePointsPerStep());
	}

	private int convertTimeToXPosition(double d) {
		return convertPointToXPosition(mySoundFile.toPointNumber(d));
	}

	private double filePointsPerStep() {
		return ((mySoundFile.giveLengthInPoints()) / (myXSize * myZoom));
	}

	private void paintSelection() {
		myDrawer.setColor(150, 50, 50, 100);
		int xMin = convertPointToXPosition(mySelectionStart);
		int xMax = convertPointToXPosition(mySelectionEnd);

		int index1 = org.amse.ys.sndeditor.ui.R.drawable.selection_start;
		int index2 = org.amse.ys.sndeditor.ui.R.drawable.selection_end;
		myDrawer.drawRectangle(xMin, 0, xMax, myYSize);
		if (xMax - xMin > myMarkerWidth) {

			if (xMin + myMarkerWidth > 0) {
				myDrawer.drawPicture(index1, xMin - myMarkerWidth / 2,
						myYSize / 2);
			}
			if (xMax - myMarkerWidth < myXSize) {
				myDrawer.drawPicture(index2, xMax - myMarkerWidth / 2,
						myYSize / 2);
			}
		} else {
			// we draw smth between
			int middle = (xMin + xMax) / 2;
			if ((middle > 0) && (middle < myXSize)) {
				myDrawer.drawPicture(index1, middle - myMarkerWidth,
						myYSize / 2);
				myDrawer.drawPicture(index2, middle, myYSize / 2);
			}

		}

	}

	private void paintHistogram(int start, int chanel) {
		myDrawer.setColor(255, 0, 255, 0);
		int numberOfPoints = mySoundFile.giveLengthInPoints();
		int ySize = myYSize;
		int xSize = myXSize;
		double filePointsPerStep = filePointsPerStep();
		int calibrationFactor = mySoundFile.giveNumberOfChannels()
				* (1 << mySoundFile.giveDepthInBits()) / ySize;
		int calibrationShift = myYSize
				/ (2 * mySoundFile.giveNumberOfChannels());
		calibrationShift += (myYSize * chanel)
				/ (mySoundFile.giveNumberOfChannels());

		if (filePointsPerStep < 1) {
			// we draw every point
			int previousY = calibrationShift;
			int previousX = 0;
			for (int i = start; previousX < xSize; i += 1) {
				int x = (int) ((i - start) / filePointsPerStep);
				int y;
				if (i < numberOfPoints) {
					y = (mySoundFile.giveIntegerIntensityAtPoint(i, chanel)
							/ calibrationFactor + calibrationShift);
				} else {
					y = calibrationShift;
				}

				myDrawer.drawLine(previousX, previousY, x, y);
				if (filePointsPerStep < 0.2) {
					myDrawer.drawCircle(x, y, 2);
				}
				previousX = x;
				previousY = y;

			}
		} else {
			int previousY = calibrationShift;
			for (int i = 0; i < xSize; i += 1) {
				int y;
				int positionInSoundFile = (int) (start + i * filePointsPerStep);
				if (positionInSoundFile < numberOfPoints) {
					y = (mySoundFile.giveIntegerIntensityAtPoint(
							positionInSoundFile, chanel)
							/ calibrationFactor + calibrationShift);
				} else {
					// y = calibrationShift;
					break;
				}
				myDrawer.drawLine((i - 1), previousY, i, y);
				previousY = y;
			}
		}
	}

	private String giveGridDescription(double positionInSeconds, int scaleIndex) {
		positionInSeconds = positionInSeconds / MAGIC_MODIFICATOR[scaleIndex];
		String s;
		if (MAGIC_HAS_DECIMALS[scaleIndex]) {
			s = (int) (positionInSeconds) + "."
					+ (int) (positionInSeconds * 10) % 10;
		} else {
			s = "" + (int) (positionInSeconds);
		}
		return (s + " " + MAGIC_NAMES[scaleIndex]);
	}

	private void drawGrid(int start) {
		myDrawer.setColor(150, 255, 255, 255);
		int tmp = myYSize / mySoundFile.giveNumberOfChannels();
		for (int i = 0; i < mySoundFile.giveNumberOfChannels(); i++) {
			myDrawer.drawLine(0, tmp * i + tmp / 2, myXSize, tmp * i + tmp / 2);
		}
		// here we determine the frequency of lines.

		double totalLengthInSeconds = mySoundFile.toSeconds(mySoundFile
				.giveLengthInPoints());
		double lengthInSeconds = totalLengthInSeconds / myZoom;

		int scaleIndex = 0;

		while (scaleIndex < MAGIC_NUMBERS_IN_SECONDS.length) {
			if (lengthInSeconds / MAGIC_NUMBERS_IN_SECONDS[scaleIndex] > 10) {
				// the scale should be larger
				scaleIndex++;
			} else {
				// more accurate check, counting width
				double gridWidth = MAGIC_NUMBERS_IN_SECONDS[scaleIndex];
				int j = (int) (mySoundFile.toSeconds(start) / gridWidth);
				int countedLength = 0;
				int xPosition = convertTimeToXPosition(j * gridWidth);
				while (xPosition < myXSize) {
					if (xPosition > 0) {
						String text = giveGridDescription(j * gridWidth,
								scaleIndex);
						countedLength += myDrawer.measureTextWidth(text);
					}
					j++;
					xPosition = convertTimeToXPosition(j * gridWidth);
				}
				if (countedLength >= myXSize) {
					scaleIndex++;
				} else {
					break;
				}
			}
		}
		double gridWidth = MAGIC_NUMBERS_IN_SECONDS[scaleIndex];

		gridWidth = MAGIC_NUMBERS_IN_SECONDS[scaleIndex];
		int j = (int) (mySoundFile.toSeconds(start) / gridWidth);
		int xPosition = convertTimeToXPosition(j * gridWidth);
		while (xPosition < myXSize) {
			if (xPosition > 0) {
				String text = giveGridDescription(j * gridWidth, scaleIndex);
				int width = myDrawer.measureTextWidth(text);
				myDrawer.setColor(255, 255, 255, 255);
				if (mySoundFile.giveNumberOfChannels() == 1) {
					myDrawer.drawText(text, xPosition - width / 2, 100);
				} else {
					myDrawer.drawText(text, xPosition - width / 2, myYSize / 2);
				}
				myDrawer.setColor(150, 255, 255, 255);
				myDrawer.drawLine(xPosition, 0, xPosition, myYSize);

			}
			j++;
			xPosition = convertTimeToXPosition(j * gridWidth);
		}

	}

	public void onDPadKeyPressed() {
	}

	public boolean isSelected() {
		return (mySelectionEnd - mySelectionStart > 0);
	}

	public int getMenuItemIconIndex(int numberOfItem) {
		switch (numberOfItem) {
		case 0:
			return org.amse.ys.sndeditor.ui.R.drawable.home;
		case 1:
			return org.amse.ys.sndeditor.ui.R.drawable.copy;
		case 2:
			return org.amse.ys.sndeditor.ui.R.drawable.paste;
		case 3:
			return org.amse.ys.sndeditor.ui.R.drawable.delete;
		case 4:
			return org.amse.ys.sndeditor.ui.R.drawable.save;
		}

		return -1;
	}

	@Override
	public ISoundFile getSoundFile() {		
		return mySoundFile;
	}

}
